﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Utils.App;
using System.Reflection;
using Utils.Collections;

namespace Utils.States
{
    public class StateMachine
    {
        /*
         * static
         */
        const int MAX_STACK_DEPTH = 8;

        class ExecStackEntry
        {
            public State ActualState;
            public State DesiredState;

            public bool CheckBothEqual() { return ActualState == DesiredState; }
        }

        static Pool<StateTransitionEvent> StateTransitionEventPool = new Pool<StateTransitionEvent>(8);
        public static EventHandler<StateTransitionEvent> UniversalTransitionEvent;

        /*
         * Fields
         */
        int execStackDesiredCounter;
        int execStackActualCounter;
        ExecStackEntry[] execStack = new ExecStackEntry[MAX_STACK_DEPTH];
        State root;
        GameObject go;
        StateMachineCtx ctx;

        //[Conditional("DEBUG")]
        string protocoll;

        /*
         * Properties
         */
        //[Conditional("DEBUG")]
        public string Protocoll { get { return protocoll; } }

        public EventHandler<StateTransitionEvent> SpecificTransitionEvent;
        public State Root { get { return root; } }

        public StateMachine(GameObject go)
        {
            this.go = go;
            ctx = new StateMachineCtx();
            root = new State();
            root.EventTransition += OnEventTransition;

            for (int i = 0; i < MAX_STACK_DEPTH; i++)
            {
                execStack[i] = new ExecStackEntry();
            }
        }

        public void Reset()
        {
            for (int i = 0; i < MAX_STACK_DEPTH; i++)
            {
                execStack[i].ActualState = null;
                execStack[i].DesiredState = null;
            }
            execStackActualCounter = 0;
        }

        public bool IsInState(StateId stateId)
        {
            for (int i = 0; i < execStackActualCounter; i++)
            {
                if (execStack[i].ActualState.ID == stateId)
                {
                    return true;
                }
            }
            return false;
        }

        public State GetStateById(StateId stateId)
        {
            for (int i = 0; i < execStackActualCounter; i++)
            {
                if (execStack[i].ActualState.ID == stateId)
                {
                    return execStack[i].ActualState;
                }
            }
            return root.GetStateById(stateId);
        }

        public void Update(float dTime)
        {
            ProtocollFrame(ref protocoll);

            ctx.DTime = dTime;

            if (execStackActualCounter == 0)
            {
                ExecStackBuildDesired(root);
                ExecStackProcessExit(ctx);
                ExecStackProcessEnter(ctx);
            }

            ExecStackUpdate(ctx);

            Transition t = null;

            int stackIdx = 1;
            while (stackIdx < execStackActualCounter && t == null)
            {
                t = execStack[stackIdx++].ActualState.FindTransition(ctx);
            }

            if (t != null)
            {
                DoTransition(t, ctx);
            }

            ctx.Clear();
        }

        void OnEventTransition(object sender, EventTransitionArgs args)
        {
            DoTransition(args.Transition, args.Ctx);
        }

        void DoTransition(Transition t, StateMachineCtx ctx)
        {
            ProtocollTransition(ref protocoll, t);

            ExecStackBuildDesired(t.To);

            State source = execStack[execStackActualCounter - 1].ActualState;

            ExecStackProcessExit(ctx);

            if (t.Activity != null)
            {
                ProtocollTf(ref protocoll);
                t.Activity();
            }
            if (t.ActivityCtx != null)
            {
                ProtocollTf(ref protocoll);
                t.ActivityCtx(ctx);
            }

            ExecStackProcessEnter(ctx);

            State target = execStack[execStackActualCounter - 1].ActualState;

            StateTransitionEvent e = StateTransitionEventPool.Get();
            e.Init(source, target);

            if (UniversalTransitionEvent != null)
            {
                UniversalTransitionEvent(go, e);
            }
            if (SpecificTransitionEvent != null)
            {
                SpecificTransitionEvent(go, e);
            }

            StateTransitionEventPool.Redeem(e);
        }

        void ExecStackBuildDesired(State to)
        {
            if (to.IsHistoryState())
            {
                State history;
                if(to.ID == State.STATE_DeepHistory)
                {
                    history = to.History;
                }
                else 
                {
                    history = to.GetDeepHistory();
                }

                if (history == null)
                {
                    State initState = to.Parent.GetInitState();
                    if(initState != null)
                    {
                        to = initState;
                    }
                    else
                    {
                        to = to.Parent;
                    }
                }
            }

            execStackDesiredCounter = to.NodeDepth + 1;
            Debug.Assert(execStackDesiredCounter <= MAX_STACK_DEPTH, "Stack is to deep!");

            State _to = to;
            do
            {
                int idx = _to.NodeDepth;

                execStack[idx].DesiredState = _to;

                _to = _to.Parent;
            } while (_to != null);

            State initChild = to.GetInitState();
            while(execStackDesiredCounter < MAX_STACK_DEPTH && initChild != null)
            {
                execStack[execStackDesiredCounter++].DesiredState = initChild;

                initChild = initChild.GetInitState();
            }
        }

        void ExecStackProcessEnter(StateMachineCtx ctx)
        {
            while (execStackActualCounter < execStackDesiredCounter)
            {
                int idx = execStackActualCounter++;

                execStack[idx].ActualState = execStack[idx].DesiredState;
                ProtocollStateActivity(ref protocoll, execStack[idx].ActualState, ".exit");

                execStack[idx].ActualState.DoEnter(ctx);
            }

            execStackDesiredCounter = 0;
        }

        void ExecStackProcessExit(StateMachineCtx ctx)
        {
            int idx = execStackActualCounter - 1;
            while(idx >= 0 && (idx >= execStackDesiredCounter || !execStack[idx].CheckBothEqual()))
            {
                State s = execStack[idx].ActualState;

                ProtocollStateActivity(ref protocoll, s, ".exit");
                s.DoExit(ctx);

                if(s.Parent != null)
                {
                    s.Parent.History = s;
                }

                idx--;
            }

            execStackActualCounter = idx + 1;
        }

        void ExecStackUpdate(StateMachineCtx ctx)
        {
            for (int i = 1; i < execStackActualCounter; i++)
            {
                ProtocollStateActivity(ref protocoll, execStack[i].ActualState, ".exec");

                execStack[i].ActualState.Update(ctx.DTime);
                execStack[i].ActualState.DoExec(ctx);
            }
        }

        // Protocolling procudes a lot of GC garbage
        [Conditional("DEBUG")]
        void ProtocollFrame(ref string prot)
        {
            //prot += "\n\n#; ";
        }
        
        [Conditional("DEBUG")]
        void ProtocollTf(ref string prot)
        {
            //prot += "<tf>.exec; ";
        }

        [Conditional("DEBUG")]
        void ProtocollTransition(ref string prot, Transition t)
        {
            //prot += "T:" + t.From.ID.Name + "->" + t.To.ID.Name + "); ";
        }

        [Conditional("DEBUG")]
        void ProtocollStateActivity(ref string prot, State state, string activiyName)
        {
            //prot += state.ID.Name + activiyName + "; ";
        }

        //[Conditional("DEBUG")]
        //string GetStateName(StateId id, GameObject go)
        //{
        //    FieldInfo[] fields = go.GetType().GetFields();
        //    for (int i = 0; i < fields.Count(); i++)
        //    {
        //        StateId stateId = fields[i].GetValue(null) as StateId;
        //        if (stateId != null && stateId == id)
        //        {
        //            return fields[i].Name;
        //        }
        //    }
        //    return "STATE";
        //}
    }
}
